//Examples/decoder.cpp

/* 
This program takes as input the inputfile specified in a
previous run of encoder.cpp. It is the companion program
of encoder.cpp, which creates k+m files.  This program assumes 
that up to m erasures have occurred in the k+m files.  It
reads in the k+m files or marks the file as erased. It then
recreates the original file and creates a new file with the
suffix "decoded" with the decoded contents of the file.

This program does not error check its command line argument.
It assumes that encoder.cpp was previously called with the given
input file and therefore the necessary metadata for the file
exists.
*/

#include <vector>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <signal.h>
#include <vector>
#include "jerasure.h"
#include "reed_sol.h"
#include "galois.h"
#include "bitmatrices.h"

#define N 8

enum Coding_Technique {Reed_Sol_Van, Reed_Sol_R6_Op, Cauchy_Orig, Cauchy_Good, R6_Min_Density, Gen_RDP, Gen_EVENODD, No_Coding};

char *Methods[N] = {(char *)"reed_sol_van", (char *)"reed_sol_r6_op", (char *)"cauchy_orig", (char *)"cauchy_good", (char *)"r6_min_density", (char *)"gen_rdp", (char *)"gen_evenodd", (char *)"no_coding"};

/* Global variables for signal handler */
enum Coding_Technique method;
int readins, readin_count;

/* Function prototype */
void ctrl_bs_handler(int dummy);

int main (int argc, char **argv) {

	FILE * fp_decoded;
	FILE * fp;

	/* Jerasure arguments */
	JER_Slices * slices;
	JER_Gen_T * g;
	vector <int> erased;
	vector <int> erasures;
	vector <int> survivors;
	
	/* Parameters */
	enum Coding_Technique tech;
	char *c_tech;
	int k, m, w, n;
	int disk_id;
	int result;
	unsigned long packets_per_slice;
	unsigned long packetsize;
	unsigned long buffersize;

	int i, j;				// loop control variables
	unsigned long bytes_to_write;
	unsigned long blocksize;			// size of individual files
	unsigned long filesize_orig;			// size of file before padding
	unsigned long total;				// used to write data, not padding to file
		
	/* Used to recreate file names */
	char *temp;
	char *cs1, *cs2;
	char *fname;
	int md;
	char *curdir;

	/* Used to time decoding */
	struct timeval t_total_start, t_total_end, t_decode_start, t_decode_end;
	double time_decode; //time that we are in slices->Decode()
	double time_total; //total time for this program to run
	double dbl_blocksize;
	double MB_decoded;

	gettimeofday(&t_total_start, NULL);

	signal(SIGQUIT, ctrl_bs_handler);

	time_decode = 0;
	time_total = 0;

	/* Error checking parameters */
	if (argc != 2) {
		fprintf(stderr, "usage: inputfile\n");
		exit(0);
	}
	curdir = (char *)malloc(sizeof(char)*100);
	getcwd(curdir, 100);
	
	/* Begin recreation of file names */
	cs1 = (char*)malloc(sizeof(char)*strlen(argv[1]));
	cs2 = strrchr(argv[1], '/');
	if (cs2 != NULL) {
		cs2++;
		strcpy(cs1, cs2);
	}
	else {
		strcpy(cs1, argv[1]);
	}
	cs2 = strchr(cs1, '.');
	if (cs2 != NULL) {
		*cs2 = '\0';
	}	
	cs2 = (char*)malloc(sizeof(char)*strlen(argv[1]));
	fname = strchr(argv[1], '.');
	if(fname != NULL){
		strcpy(cs2, fname);
	}
	fname = (char *)malloc(sizeof(char*)*(strlen(curdir)+strlen(argv[1])+18));

	/* Read in parameters from metadata file */
	sprintf(fname, "%s/Coding/%s_meta.txt", curdir, cs1);

	fp = fopen(fname, "rb");
	if(fp == NULL){
		fprintf(stderr,"Could not open %s\n",fname);
		exit(0);
	}
	temp = (char *)malloc(sizeof(char)*(strlen(argv[1])+10));
	fscanf(fp, "%s", temp);	
	
	if (fscanf(fp, "%lu", &filesize_orig) != 1) {
		fprintf(stderr, "Original size is not valid\n");
		exit(0);
	}
	if (fscanf(fp, "%d %d %d %lu %lu", &k, &m, &w, &packetsize, &buffersize) != 5) {
		fprintf(stderr, "Parameters are not correct\n");
		exit(0);
	}
	c_tech = (char *)malloc(sizeof(char)*(strlen(argv[1])+10));
	fscanf(fp, "%s", c_tech);
	fscanf(fp, "%d", (int *)(&tech));
	method = tech;
	fscanf(fp, "%d", &readins);
	fclose(fp);	

	sprintf(temp, "%d", k);
	md = strlen(temp);

	/* Create coding matrix or bitmatrix */
	switch(tech) {
		case No_Coding:
			break;
		case Reed_Sol_Van:
			g = RS_Classic_Generator(k,m,w); 
			break;
		case Cauchy_Orig:
			g = CRS_Generator(k,m,w);
			CRS_Convert_To_Bitmatrix(*g);
			break;
		case Cauchy_Good:
			g = CRS_Good_Generator_Bitmatrix(k,m,w);
			break;	
		case Reed_Sol_R6_Op:
			g = RS_R6_Generator(k,w);
			break;	
		case R6_Min_Density:
			g = R6_Min_Density_Generator(k,w);
			break;
		case Gen_RDP:
			g = Gen_RDP_Generator(k, m, w);
			break;
		case Gen_EVENODD:
			g = Gen_Evenodd_Generator(k, m, w);
			break;
		
	}
	if(g == NULL){
		fprintf(stderr,"Error: could not create generator\n");
		exit(1);
	}

	//create slices
	n = k+m;
	blocksize = buffersize/k;
	packets_per_slice = blocksize / packetsize;
	slices = new JER_Slices(n, k, packetsize ,packets_per_slice,g);

	//set coding drives to 'up' (data drives are up by default)
	for(i=k;i<n;i++){
		slices->States[i] = 0;
	}
	//allocate memory for all disks
	for (i = 0; i < n; i++) {
		slices->Ptrs[i] = (unsigned char *)malloc(sizeof(unsigned char)*blocksize);
	}

	/*
	Check which of the n files we still have
	*/
	erased.resize(n,0);
	for(i = 0;i < n;i++){

		//get the filename
		if(i<k){
			//data drive
			sprintf(fname, "%s/Coding/%s_k%0*d%s", curdir, cs1, md, i+1, cs2);
		}else{
			//coding drive
			sprintf(fname, "%s/Coding/%s_m%0*d%s", curdir, cs1, md, i+1-k, cs2);
		}

		//try to open the file
		fp = fopen(fname, "rb");
		if (fp == NULL) {
			//The file was erased
			erased[i] = 1;
			erasures.push_back(i);
		}else{
			//The file was found
			survivors.push_back(i);
			fclose(fp);
		}
	}

	if(survivors.size() < k){
		fprintf(stderr,"Error: cannot decode, more than m files are missing\n");
		exit(1);
	}
	
	/* Begin the decoding process */
	total = 0;
	readin_count = 1;
	while (readin_count <= readins) {

		//set the failed drives to 'down'
		//we must do this before every decode, because decode sets the drives to 'up'
		for(i=0;i<erasures.size();i++){
			slices->Add_Drive_Failure(erasures[i]);
		}

		//read from the files that still exist
		for(i=0;i<survivors.size();i++){
			disk_id = survivors[i];
			if(disk_id<k){
				//data drive
				sprintf(fname, "%s/Coding/%s_k%0*d%s", curdir, cs1, md, disk_id+1, cs2);
			}else{
				//coding drive
				sprintf(fname, "%s/Coding/%s_m%0*d%s", curdir, cs1, md, disk_id+1-k, cs2);
			}

			//try to open the file
			fp = fopen(fname, "rb");
			if(fp == NULL){
				//should never get here, we were able to open the file above
				fprintf(stderr,"Error: could not open file: %s\n",fname);
				exit(1);
			}
			fseek(fp, blocksize*(readin_count-1), SEEK_SET); 
			fread(slices->Ptrs[disk_id], sizeof(unsigned char), blocksize, fp);
			fclose(fp);
		}

		/* Choose proper decoding method */
		switch(tech){
			case Reed_Sol_Van:
			case Reed_Sol_R6_Op:
				gettimeofday(&t_decode_start, NULL);
				result = slices->Decode();
				gettimeofday(&t_decode_end, NULL);
				break;
			case Cauchy_Orig:
			case Cauchy_Good:
			case R6_Min_Density:
			case Gen_RDP:
			case Gen_EVENODD:
				gettimeofday(&t_decode_start, NULL);
				result = slices->Decode_Schedule_Lazy(1);
				gettimeofday(&t_decode_end, NULL);
				break;
			default:
				fprintf(stderr, "Invalid coding technique.\n");
				exit(0);
				break;
		}

		time_decode += t_decode_end.tv_usec;
		time_decode -= t_decode_start.tv_usec;
		time_decode /= 1000000.0;
		time_decode += t_decode_end.tv_sec;
		time_decode -= t_decode_start.tv_sec;
	
		/* Exit if decoding was unsuccessful */
		if (result == -1) {
			fprintf(stderr, "Decode failed\n");
			exit(0);
		}

		/* Create decoded file */
		sprintf(fname, "%s/Coding/%s_decoded%s", curdir, cs1, cs2);
		if (readin_count == 1) {
			//new file
			fp = fopen(fname, "wb");
		}
		else {
			//append
			fp = fopen(fname, "ab");
		}
		for (i = 0; i < k; i++) {
			//block contains some padding
			bytes_to_write = filesize_orig-total;
			if (bytes_to_write > blocksize) {
				//write this entire block
				bytes_to_write = blocksize; 
			}
			fwrite(slices->Ptrs[i], 1, bytes_to_write, fp);
			total+= bytes_to_write;
		}
		fclose(fp);

		readin_count++;
	}
	
	/* Free allocated memory */
	for(i=0;i<n;i++){
		free(slices->Ptrs[i]);
	}
	free(curdir);
	free(cs1);
	free(cs2);
	free(fname);
	free(temp);
	free(c_tech);
	delete g;
	delete slices;

	/* Stop timing and print time */
	gettimeofday(&t_total_end, NULL);
	time_total += t_total_end.tv_usec;
	time_total -= t_total_start.tv_usec;
	time_total /= 1000000.0;
	time_total += t_total_end.tv_sec;
	time_total -= t_total_start.tv_sec;

	MB_decoded = (double)(blocksize*erasures.size());
	MB_decoded /= 1024;
	MB_decoded /= 1024;
	if(MB_decoded != 0){
		printf("Decode rate (MB/sec): %0.10f\n", MB_decoded/time_decode);
	}
	printf("Overall rate (MB/sec): %0.10f\n\n", MB_decoded/time_total);
}	

void ctrl_bs_handler(int dummy) {
	time_t mytime;
	mytime = time(0);
	fprintf(stderr, "\n%s\n", ctime(&mytime));
	fprintf(stderr, "You just typed ctrl-\\ in decoder.c\n");
	fprintf(stderr, "Total number of read ins = %d\n", readins);
	fprintf(stderr, "Current read in: %d\n", readin_count);
	fprintf(stderr, "Method: %s\n\n", Methods[method]);
	signal(SIGQUIT, ctrl_bs_handler);
}
